#include <system.h>

enum c328r_enums {
	
	C328R_CMD_INIT = 0x01,
	
	C328R_CMD_GETPIC = 0x04,
	C328R_CMD_SNAP,
	C328R_CMD_SIZE,
	C328R_CMD_BAUD,
	C328R_CMD_RESET,
	C328R_CMD_POFF,
	C328R_CMD_DATA,
	
	C328R_CMD_SYNC = 0x0d,
	C328R_CMD_ACK,
	C328R_CMD_NAK,
	
	C328R_CMD_LIGHT = 0x13,
	
	C328R_STATE_STOP = 0x20,
	C328R_STATE_SYNC,
	C328R_STATE_SYNC_ACK,
	C328R_STATE_STARTED,
	C328R_STATE_CONFIG,
	C328R_STATE_GETPIC,
	
	C328R_SYNC_DELAY = 50, // milliseconds.
	C328r_SYNC_MAX = 60,
	
};


/**
 *  Local prototype functions
 */

void C328R_SERIAL_streamdone(error_t);
void C328R_SERIAL_receive(uint8_t);

void C328R_TIMERSYNC_run(void);

error_t C328R_command(uint8_t id, uint8_t param1, uint8_t param2,
                      uint8_t param3, uint8_t param4);

void C328R_recvCommand(uint8_t data);
void C328R_recvAck(void);
void C328R_recvSync(void);
void C328R_recvData(void);


/**
 *  Local variables
 */
timer_t c328r_timerSync;
uint8_t c328r_syncCount;

uint8_t c328r_state;

uint8_t c328r_color, c328r_res;

c328r_t c328r_txpkt, c328r_rxpkt;

void (*c328r_startdone)(error_t);
void (*c328r_getdone)(error_t, uint32_t);
void (*c328r_receive)(void);

uint8_t c328r_rxlen;

uint8_t* c328r_picBuff;


/******************************************************************************
 *                           FUNCTION IMPLEMENTATION
 */

void C328R_init(void) {
	
	SERIAL1_init();
	SERIAL1_reg(NULL, C328R_SERIAL_streamdone, C328R_SERIAL_receive);
	
	TIMER_reg(&c328r_timerSync, C328R_TIMERSYNC_run);
	
	c328r_state = C328R_STATE_STOP;
}


void C328R_reg(void (*startdone)(error_t), void (*getdone)(error_t, uint32_t),
               void (*receive)(void)) {
	
	if (startdone != NULL)
		c328r_startdone = startdone;
	
	if (getdone != NULL)
		c328r_getdone = getdone;
	
	if (receive != NULL)
		c328r_receive = receive;
}


error_t C328R_start(uint8_t color, uint8_t res) {
	
	if (c328r_state != C328R_STATE_STOP)
		return FAIL;
	
	c328r_color = color;
	c328r_res = res;
	
	c328r_state = C328R_STATE_SYNC;
	
	c328r_syncCount = 0;
	error_t err = C328R_command(C328R_CMD_SYNC, 0, 0, 0, 0);
	
	if (err != SUCCESS)
		c328r_state = C328R_STATE_STOP;
	
	return err;
}


error_t C328R_getPicture(uint8_t type, uint8_t* buff) {
	
	if (c328r_state != C328R_STATE_STARTED)
		return FAIL;
	
	c328r_state = C328R_STATE_GETPIC;
	
	error_t err = C328R_command(C328R_CMD_GETPIC, type, 0, 0, 0);
	
	if (err != SUCCESS) {
		c328r_state = C328R_STATE_STARTED;
	}
	
	return err;
}


void C328R_TIMERSYNC_run(void) {
	
	if (c328r_state != C328R_STATE_SYNC)
		return;
	
	if (++c328r_syncCount > C328r_SYNC_MAX) {
		c328r_state = C328R_STATE_STOP;
		c328r_startdone(ERETRY);
		
		return;
	}
	
	error_t err = C328R_command(C328R_CMD_SYNC, 0, 0, 0, 0);
	
	if (err != SUCCESS) {
		c328r_state = C328R_STATE_STOP;
		c328r_startdone(err);
	}
}


error_t C328R_command(uint8_t id, uint8_t param1, uint8_t param2,
                      uint8_t param3, uint8_t param4) {
	
	c328r_command_t* cmd = (c328r_command_t*)(&c328r_txpkt);
	
	cmd->delimiter = 0xaa;
	cmd->id = id;
	
	cmd->param[0] = param1;
	cmd->param[1] = param2;
	cmd->param[2] = param3;
	cmd->param[3] = param4;
	
	c328r_rxlen = 0;
	
	return SERIAL1_sendStream((uint8_t*) &c328r_txpkt, sizeof(c328r_command_t));
}


void C328R_SERIAL_streamdone(error_t err) {
	
	switch (c328r_state) {
		
	case C328R_STATE_SYNC:
		
		TIMER_start(&c328r_timerSync, C328R_SYNC_DELAY);
		break;
		
	case C328R_STATE_SYNC_ACK:
		
		c328r_state = C328R_STATE_CONFIG;
		
		err = C328R_command(C328R_CMD_INIT, 0, c328r_color, c328r_res, c328r_res);
		
		if (err != SUCCESS) {
			c328r_state = C328R_STATE_STOP;
			c328r_startdone(err);
		}
		
		break;
		
	case C328R_STATE_CONFIG:
		
		c328r_state = C328R_STATE_STARTED;
		c328r_startdone(err);
		
		break;
		
	case C328R_STATE_GETPIC:
		
		if (err != SUCCESS) {
			
			c328r_state = C328R_STATE_STARTED;
			c328r_getdone(err, 0);
		}
		
		break;
	}
}


void C328R_recvCommand(uint8_t data) {
	
	uint8_t* cmd = (uint8_t*) &c328r_rxpkt;
	
	if (c328r_rxlen == 0) {
		
		if (data != 0xaa)
			return;
		
		c328r_rxlen++;
		cmd[0]= data;
	}
	else {
		
		cmd[c328r_rxlen++] = data;
		
		if (c328r_rxlen == sizeof(c328r_command_t)) {
			
			switch (cmd[1]) {
				
			case C328R_CMD_ACK:
				C328R_recvAck();
				break;
				
			case C328R_CMD_SYNC:
				C328R_recvSync();
				break;
				
			case C328R_CMD_DATA:
				C328R_recvData();
				break;
			}
		}
	}
	
}


void C328R_recvAck(void) {
	
	switch (c328r_state) {
		
	case C328R_STATE_SYNC:
		
		c328r_state = C328R_STATE_SYNC_ACK;
		TIMER_stop(&c328r_timerSync);
		
		c328r_rxlen = 0;
		
		break;
		
	case C328R_STATE_GETPIC:
		break;
	}
}


void C328R_recvSync(void) {
	
	if (c328r_state != C328R_STATE_SYNC_ACK)
		return;
	
	error_t err = C328R_command(C328R_CMD_ACK, C328R_CMD_SYNC, 0, 0, 0);
	
	if (err != SUCCESS) {
		
		c328r_state = C328R_STATE_STOP;
		c328r_startdone(err);
	}
}

void C328R_recvData(void) {
}


void C328R_SERIAL_receive(uint8_t data) {
	
	switch (c328r_state) {
		
	default:
		C328R_recvCommand(data);
		break;
	}
}
